package com.momo.seckilling.service;

import com.momo.seckilling.dao.SeckillProductDao;
import com.momo.seckilling.entity.SeckilUserResult;
import com.momo.seckilling.entity.SeckillProductInfo;
import com.momo.seckilling.utils.RedisLockUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.*;
import java.util.concurrent.*;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * Created by li on 2019/3/9. 所有的秒杀实现
 */
@Service
public class SeckillByService {

    public SeckillByService(){
        new Thread(new ConsumerQueueRequest()).start();
    }
    @Autowired
    private SeckillProductDao seckillProductDao;

//    @Autowired
//    private ProductService productService;

    @Autowired
    private SeckillUserResultService seckillUserResultService;

    private Lock lock = new ReentrantLock();

    private ConcurrentHashMap<String,Integer> cacheMap = new ConcurrentHashMap<String,Integer>();

    private BlockingQueue<QueueRequest> seckillQueue = new LinkedBlockingDeque<QueueRequest>();

    private Map<Integer,Future> cacheSeckillResultMap = new HashMap<Integer,Future>();

    private  ExecutorService executorService = Executors.newCachedThreadPool();

    private static final String Inventory = "Inventory";
    private static final String Seckillnum = "Seckillnum";

    @Transactional
    public Map<String,String> procedureLockBy(int userid, int id){
        Map<String,String> dataMap = new HashMap<String,String>();
        lock.lock();
        try {
            SeckillProductInfo seckillProductInfo = seckillProductDao.findSeckillInfoById(id);
            long seckillInventory = seckillProductInfo.getSeckillInventory();//库存
            int seckillnum = seckillProductInfo.getSeckillnum();//秒杀数量
            seckillnum++;
            if (seckillnum > seckillInventory) {
                System.out.println("卖光了,谢谢惠顾！！");
                dataMap.put("flag","fail");
                dataMap.put("data","卖光了,谢谢惠顾！！");
                return dataMap;
            }
            SeckillProductInfo seckillProductInfoUpate = new SeckillProductInfo();
            seckillProductInfoUpate.setId(id);
            seckillProductInfoUpate.setSeckillnum(seckillnum);
            seckillProductDao.updateSeckillInfoBySeckNum(seckillProductInfoUpate);
            dataMap.put("flag","success");
            dataMap.put("data","秒杀成功");

        }finally {
            lock.unlock();
        }

        return dataMap;

    }


    @Transactional
    public Map<String,String> procedureLockByAOP(int userid, int id){
        Map<String,String> dataMap = new HashMap<String,String>();
            SeckillProductInfo seckillProductInfo = seckillProductDao.findSeckillInfoById(id);
            long seckillInventory = seckillProductInfo.getSeckillInventory();//库存
            int seckillnum = seckillProductInfo.getSeckillnum();//秒杀数量
            seckillnum++;
            if (seckillnum > seckillInventory) {
                System.out.println("卖光了,谢谢惠顾！！");
                dataMap.put("flag","fail");
                dataMap.put("data","卖光了,谢谢惠顾！！");
                return dataMap;
            }
            SeckillProductInfo seckillProductInfoUpate = new SeckillProductInfo();
            seckillProductInfoUpate.setId(id);
            seckillProductInfoUpate.setSeckillnum(seckillnum);
            seckillProductDao.updateSeckillInfoBySeckNum(seckillProductInfoUpate);
            dataMap.put("flag","success");
            dataMap.put("data","秒杀成功");

        System.out.println("进入 procedureLockByAOP");


        return dataMap;

    }



    public void multiThread(int userid, int id){
        Integer inventorynum = cacheMap.get(Inventory+id);//库存
        if(inventorynum == null){
            SeckillProductInfo seckillProductInfo = seckillProductDao.findSeckillInfoById(id);
            cacheMap.put(Inventory+id,Integer.valueOf(seckillProductInfo.getSeckillInventory()+""));
            cacheMap.put(Seckillnum+id,seckillProductInfo.getSeckillnum());
        }
        inventorynum = cacheMap.get(Inventory+id);//库存
        Integer seckillnum = cacheMap.get(Seckillnum+id);//秒杀数量
        seckillnum++;
        cacheMap.put(Seckillnum+id,seckillnum);
        new Thread(new SeckillThread(userid,seckillnum,inventorynum,id)).start();

    }


    @Transactional
    public Map<String,String> pessimismLock(int userid, int id){
        Map<String,String> dataMap = new HashMap<String,String>();
        SeckillProductInfo seckillProductInfo = seckillProductDao.selectForUpdate(id);
        long seckillInventory = seckillProductInfo.getSeckillInventory();
        int seckillnum = seckillProductInfo.getSeckillnum();
        if(seckillnum>=seckillInventory){
            System.out.println("卖光了,谢谢惠顾！！");
            dataMap.put("flag","fail");
            dataMap.put("data","卖光了,谢谢惠顾！！");
            return dataMap;
        }
        seckillnum++;
        SeckillProductInfo seckillProductInfoUpate = new SeckillProductInfo();
        seckillProductInfoUpate.setId(id);
        seckillProductInfoUpate.setSeckillnum(seckillnum);
        seckillProductDao.updateSeckillInfoBySeckNum(seckillProductInfoUpate);
        dataMap.put("flag","success");
        dataMap.put("data","秒杀成功");

        return dataMap;


    }


    @Transactional
    public Map<String,String> optimisticLock(int userid, int id){
        Map<String,String> dataMap = new HashMap<String,String>();
        SeckillProductInfo seckillProductInfo = seckillProductDao.findSeckillInfoById(id);
        int seckillversion = seckillProductInfo.getSeckillversion();
        long seckillInventory = seckillProductInfo.getSeckillInventory();
        int seckillnum = seckillProductInfo.getSeckillnum();
        if(seckillnum>=seckillInventory){
            System.out.println("卖光了,谢谢惠顾！！");
            dataMap.put("flag","fail");
            dataMap.put("data","卖光了,谢谢惠顾！！");
            return dataMap;
        }
        seckillnum++;
        SeckillProductInfo seckillProductInfoUpate = new SeckillProductInfo();
        seckillProductInfoUpate.setId(id);
        seckillProductInfoUpate.setSeckillnum(seckillnum);
        seckillProductInfoUpate.setSeckillversion(seckillversion);
        int result = seckillProductDao.updateSeckillInfoByVersion(seckillProductInfoUpate);
        if(result>0){
            dataMap.put("flag","success");
            dataMap.put("data","秒杀成功");
        }else{
            dataMap.put("flag","fail");
            dataMap.put("data","秒杀失败，请重新操作");
        }

        return dataMap;


    }


    @Transactional
    public void queueAndThread(int userid, int id){
        QueueRequest queueRequest = new QueueRequest();
        queueRequest.setUserid(userid);
        queueRequest.setId(id);
        try {
            seckillQueue.put(queueRequest);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }


    }


    @Transactional
    public Map<String,String> redisDistributeLock(int userid, int id){
        Map<String,String> dataMap = new HashMap<String,String>();
        boolean haslock = RedisLockUtil.tryGetDistributedLock(id+"",userid+"", 50000);

        if(haslock){
            processSeckill(id, dataMap);
        }else{
            boolean flag = false;
            for(int i=0;i<3;i++){
                haslock = RedisLockUtil.tryGetDistributedLock(id+"",userid+"", 50000);
                if(haslock){
                    processSeckill(id, dataMap);
                    flag = true;
                    break;
                }
            }
            if(!flag){
                dataMap.put("flag","fail");
                dataMap.put("data","太火爆了，请重新抢购");
            }
        }

        boolean releaseResult = RedisLockUtil.releaseDistributedLock(id+"",userid+"");
        while (!releaseResult){
            releaseResult = RedisLockUtil.releaseDistributedLock(id+"",userid+"");
        }
        return dataMap;

    }

    private void processSeckill(int id, Map<String, String> dataMap) {
        SeckillProductInfo seckillProductInfo = seckillProductDao.findSeckillInfoById(id);
        long seckillInventory = seckillProductInfo.getSeckillInventory();
        int seckillnum = seckillProductInfo.getSeckillnum();
        if(seckillnum>=seckillInventory){
            System.out.println("卖光了,谢谢惠顾！！");
            dataMap.put("flag","fail");
            dataMap.put("data","卖光了,谢谢惠顾！！");
            return;
        }
        seckillnum++;
        SeckillProductInfo seckillProductInfoUpdate = new SeckillProductInfo();
        seckillProductInfoUpdate.setId(id);
        seckillProductInfoUpdate.setSeckillnum(seckillnum);
        int result = seckillProductDao.updateSeckillInfoBySeckNum(seckillProductInfoUpdate);
        if(result>0){
            dataMap.put("flag","success");
            dataMap.put("data","秒杀成功");
        }else{
            dataMap.put("flag","fail");
            dataMap.put("data","秒杀失败，请重新操作");
        }
    }



    public void seckillFuture(int userid, int id){
            Future<Integer> resultFuture = executorService.submit(new SeckillFuture(userid,id));
            cacheSeckillResultMap.put(userid,resultFuture);
    }


    class SeckillThread implements Runnable{

        private int userid;
        private int seckillnum;
        private int inventorynum;
        private int productid;

        public SeckillThread(int userid,int seckillnum,int inventorynum,int productid){
            this.inventorynum = inventorynum;
            this.seckillnum = seckillnum;
            this.userid = userid;
            this.productid = productid;
        }

        @Override
        public void run() {
            SeckilUserResult seckilUserResult = new SeckilUserResult();
            SeckillProductInfo seckillProductInfo = seckillProductDao.findSeckillInfoById(productid);
            seckilUserResult.setProductid(seckillProductInfo.getProductid());
            seckilUserResult.setSeckillid(seckillProductInfo.getId());
            seckilUserResult.setUserid(userid);
            seckilUserResult.setResult(0);
            seckilUserResult.setResultdata("用户"+userid+"秒杀成功！！");
            seckilUserResult.setSecktime(new Date());
            System.out.println("用户"+userid+"开始秒杀");
            if(seckillnum > inventorynum){
                System.out.println("用户"+userid+"秒杀失败");
                seckilUserResult.setResult(1);
                seckilUserResult.setResultdata("用户"+userid+"秒杀失败");
            }else {
                SeckillProductInfo seckillProductInfoUpate = new SeckillProductInfo();
                seckillProductInfoUpate.setId(productid);
                seckillProductInfoUpate.setSeckillnum(seckillnum);
                seckillProductDao.updateSeckillInfoBySeckNum(seckillProductInfoUpate);
            }
            seckillUserResultService.saveSeckilUserResult(seckilUserResult);
        }
    }

    class QueueRequest{
        private int userid;
        private int id;

        public int getUserid() {
            return userid;
        }

        public void setUserid(int userid) {
            this.userid = userid;
        }

        public int getId() {
            return id;
        }

        public void setId(int id) {
            this.id = id;
        }
    }

    class ConsumerQueueRequest implements Runnable{

        @Override
        public void run() {
            while(!Thread.interrupted()){
                try {
                    QueueRequest queueRequest = seckillQueue.take();
                    int userid = queueRequest.getUserid();
                    int id = queueRequest.getId();
                    SeckilUserResult seckilUserResult = new SeckilUserResult();
                    SeckillProductInfo seckillProductInfo = seckillProductDao.findSeckillInfoById(id);
                    long inventorynum = seckillProductInfo.getSeckillInventory();
                    int seckillnum = seckillProductInfo.getSeckillnum();
                    seckillnum++;

                    seckilUserResult.setProductid(seckillProductInfo.getProductid());
                    seckilUserResult.setSeckillid(seckillProductInfo.getId());
                    seckilUserResult.setUserid(userid);
                    seckilUserResult.setResult(0);
                    seckilUserResult.setResultdata("用户"+userid+"秒杀成功！！");
                    seckilUserResult.setSecktime(new Date());
                    System.out.println("用户"+userid+"开始秒杀");
                    if(seckillnum > inventorynum){
                        System.out.println("用户"+userid+"秒杀失败");
                        seckilUserResult.setResult(1);
                        seckilUserResult.setResultdata("用户"+userid+"秒杀失败");
                    }else{
                        SeckillProductInfo seckillProductInfoUpate = new SeckillProductInfo();
                        seckillProductInfoUpate.setId(id);
                        seckillProductInfoUpate.setSeckillnum(seckillnum);
                        seckillProductDao.updateSeckillInfoBySeckNum(seckillProductInfoUpate);
                    }
                    seckillUserResultService.saveSeckilUserResult(seckilUserResult);
                }catch (Exception e){
                    e.printStackTrace();
                }
            }
        }
    }

    class SeckillFuture implements Callable<Integer>{
        private int userid;
        private int id;
        public SeckillFuture(int userid,int id){
            this.userid = userid;
            this.id = id;
        }
        @Override
        public Integer call() throws Exception {
            SeckilUserResult seckilUserResult = new SeckilUserResult();
            SeckillProductInfo seckillProductInfo = seckillProductDao.findSeckillInfoById(id);
            long inventorynum = seckillProductInfo.getSeckillInventory();
            int seckillnum = seckillProductInfo.getSeckillnum();
            seckillnum++;

            seckilUserResult.setProductid(seckillProductInfo.getProductid());
            seckilUserResult.setSeckillid(seckillProductInfo.getId());
            seckilUserResult.setUserid(userid);
            seckilUserResult.setResult(0);
            seckilUserResult.setResultdata("用户"+userid+"秒杀成功！！");
            seckilUserResult.setSecktime(new Date());
            System.out.println("用户"+userid+"开始秒杀");
            if(seckillnum > inventorynum){
                System.out.println("用户"+userid+"秒杀失败");
                seckilUserResult.setResult(1);
                seckilUserResult.setResultdata("用户"+userid+"秒杀失败");
            }else{
                SeckillProductInfo seckillProductInfoUpate = new SeckillProductInfo();
                seckillProductInfoUpate.setId(id);
                seckillProductInfoUpate.setSeckillnum(seckillnum);
                seckillProductDao.updateSeckillInfoBySeckNum(seckillProductInfoUpate);
            }
            seckillUserResultService.saveSeckilUserResult(seckilUserResult);
            return seckilUserResult.getResult();
        }
    }

}


